package org.helsoft.mojo;

import java.io.File;
import java.io.IOException;

import org.apache.maven.model.PatternSet;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.util.FileUtils;
import org.helsoft.mojo.api.AbstractPDEMojo;
import org.helsoft.mojo.api.JarCreator;
import org.helsoft.mojo.api.PluginDescriptor;

/**
 * This mojo reads the file build.properties and copy to the target/classes
 * directory the resources under the properties <code>build.properties</code>.
 * It's also copy jars described into the manifest under the properties
 * <code>Require-Bundle</code>.
 * 
 * @author steg
 * 
 * @goal jar
 * 
 * @phase package
 */
public class PackageMojo extends AbstractPDEMojo {

	/**
	 * @parameter expression="${project.resources.directory}"
	 *            default-value="src/main/resources"
	 * @readonly
	 * @required
	 */
	private File resourceDir;

	/**
	 * Whether or not to include source files into the package.
	 * 
	 * @parameter expression="${package.includeSources}" default-value="true"
	 */
	private Boolean includeSources;

	/**
	 * The file matching this filter into the sources directory and the
	 * resources directory will be included into the package.
	 * 
	 * @parameter expression="${package.resourceFilter}"
	 */
	private PatternSet resourcesFilter;

	@Override
	public void execute() throws MojoExecutionException, MojoFailureException {
		// We add resources from bin.includes of the build.properties
		String[] binIncludes = getBuildProperties()
				.getProperty("bin.includes", "").trim().split("\\s*,\\s*");
		for (String path : binIncludes) {
			File file = new File(path);
			addResource(file);
		}
		// We add resources from Bundle-Classpath of the manifest
		for (String classpathEntry : getPluginDescriptor().getBundleClasspath()) {
			File file = new File(classpathEntry.trim());
			addResource(file);
		}

		File sourceDir = new File(getProject().getBuild().getSourceDirectory());
		// We add resources from the sources directory
		try {
			for (File file : listFiles(sourceDir, resourcesFilter)) {
				addResource(sourceDir, file, null);
			}
		} catch (IOException e) {
			fail(e);
		}

		if (includeSources) {
			// We add sources files
			try {
				for (File file : listFiles(sourceDir, null)) {
					addResource(sourceDir, file, "src");
				}
			} catch (IOException e) {
				fail(e);
			}
		}

		//We update the plugin version with the current qualifier
		try {
			updateVersion();
		} catch (IOException e1) {
			fail(e1);
		}
		
		// We create the jar
		File jarFile = new File(getProject().getBuild().getDirectory(),
				getProject().getBuild().getFinalName() + ".jar");
		try {
			if (jarFile.isFile()) {
				FileUtils.forceDelete(jarFile);
			}
			info("Creating '%s'", jarFile);
			JarCreator.folderToJar(new File(getProject().getBuild()
					.getOutputDirectory()), jarFile, true, getLog());
		} catch (IOException e) {
			fail(e);
		}
	}

	/**
	 * @param root
	 *            The parent: the file path will be strip with this path
	 * @param file
	 *            The file name
	 * @param parentPath
	 *            A path relative to the target directory (may be
	 *            <code>null</code>)
	 * @throws MojoExecutionException
	 */
	private void addResourceFile(File root, File file, String parentPath)
			throws MojoExecutionException {
		if (!isInResourceDirectory(file)) {
			String parent = relativeParentPath(file, root);
			File parentFile = new File(getProject().getBuild()
					.getOutputDirectory(), (parentPath == null
					|| parentPath.isEmpty() ? "" : parentPath + File.separator)
					+ parent);
			if (!parentFile.isDirectory()) {
				parentFile.mkdirs();
			}

			File destination = new File(parentFile, file.getName());
			if (needUpdate(file, destination)) {
				debug("Copying '%s'", file);
				try {
					FileUtils.copyFile(file, destination);
				} catch (IOException e) {
					fail(e);
				}
			}
		}
	}

	/**
	 * @param root
	 * @param dir
	 * @param parentPath
	 * @throws MojoExecutionException
	 */
	private void addResourceDirectory(File root, File dir, String parentPath)
			throws MojoExecutionException {
		if (!isInResourceDirectory(dir) && !".".equals(dir.getPath())
				&& !dir.equals(getProject().getBasedir())) {
			try {
				for (File file : listFiles(dir, null)) {
					addResource(root, file, parentPath);
				}
			} catch (IOException e) {
				fail(e);
			}
		}
	}

	/**
	 * @param root
	 * @param file
	 * @param parentPath
	 * @throws MojoExecutionException
	 */
	private void addResource(File root, File file, String parentPath)
			throws MojoExecutionException {
		if (file.isFile() && !file.isHidden()) {
			addResourceFile(root, file, parentPath);
		} else if (file.isDirectory()) {
			addResourceDirectory(root, file, parentPath);
		}
	}

	/**
	 * 
	 * @param file
	 * @throws MojoExecutionException
	 */
	private void addResource(File file) throws MojoExecutionException {
		addResource(getProject().getBasedir(), file, null);
	}

	/**
	 * @param file
	 * @return
	 */
	public final boolean isInResourceDirectory(File file) {
		try {
			String left = file.getCanonicalPath();
			String right = resourceDir.getCanonicalPath();
			return left.startsWith(right);
		} catch (IOException e) {
			warn(e);
			return false;
		}
	}

	/**
	 * @param file
	 * @return
	 */
	public final boolean isInSourceFolder(File file) {
		try {
			String left = file.getCanonicalPath();
			String right = new File(getProject().getBuild()
					.getSourceDirectory()).getCanonicalPath();
			return left.startsWith(right);
		} catch (IOException e) {
			debug(e);
			return false;
		}
	}

	private String relativeParentPath(File path, File parent) {
		try {
			return path.getParentFile().getCanonicalPath()
					.substring(parent.getCanonicalPath().length());
		} catch (Exception e) {
			debug(e);
		}
		return "";
	}

	private boolean needUpdate(File newFile, File oldFile) {
		try {
			return !oldFile.exists()
					|| !FileUtils.contentEquals(newFile, oldFile);
		} catch (IOException e) {
			return true;
		}
	}

	private void updateVersion() throws IOException {
		File file = new File(getProject().getBuild().getOutputDirectory(), "META-INF/MANIFEST.MF");
		if (file.isFile()) {			
			PluginDescriptor plug = PluginDescriptor.fromManifest(file);
			plug.setSnapshotVersion();
			plug.saveManifest();
		}
	}
}
